#!/bin/bash
#
# Prototype for bash test library.
#
# TODO: naming conventions?
# expect-grep
# expect-file-eq
# expect-file-eq-stdin?
#
# plain "expect" is bad because there is the "expect" tool, which is also used
# for testing?
# 
# I guess they can use "command expect" to run that.

set -o nounset

log() {
  echo 1>&2 "$@"
}

die() {
  log "$@"
  exit 1
}

# TODO: we are returning 1, but not actually quitting!
# maybe we need to increment a global failure counter.  And then exit 1 if it's
# not 0.  Or just exit with the number of failures?


# Check a value using the 'test' syntax .

expect() {
  local msg=${4:-}
  if ! test "$1" "$2" "$3"; then
    # TODO: file name / function name should be in the error message?
    if test -n "$msg"; then
      log "$msg"
    else
      log "FAIL: $1 $2 $3"
    fi
    return 1
  fi 
}

# TODO: get rid of "check" alias.  This is used for something else.
check() {
  expect "$@"
}


# Check that a pattern matches a file.
check_grep() {
  local pat="$1"
  local file="$2"
  local msg=${3:-}

  # Use extended regexps always.
  # I think even better would be perl, or annex.
  grep --extended-regexp "$pat" "$file" >/dev/null
  if test $? -ne 0; then
    if test -n "$msg"; then
      log "FAIL: $msg"
    else
      log "FAIL: no line in $file matched '$pat'"
    fi
    return 1
  fi
}

# Check that an actual file equals a string.

check_file_eq() {
  local expected=$1  # string
  local file=$2  # filename
  local msg=${3:-}

  test "$expected" = "$(cat $file)"
  if test $? -ne 0; then
    if test -n "$msg"; then
      log "FAIL: $msg"
    else
      echo "$expected" | diff -u - $file 
    fi
    return 1
  fi
}

# Check file contents.  Pass the filename as the argument, and put expected
# contents on stdin.

check_file_eq_stdin() {
  local actual=$1
  local msg=${2:-"stdin differed"}

  diff -u - $actual
  exit_code=$?

  if test "$exit_code" -ne 0; then
    log "FAIL: $msg"
  fi

  # fail if diff fails
  return $exit_code
}


# Generate a script that runs all tests sequentially.
#
# TODO:
# - Maybe start them in parallel with xargs?  What about logging?
# - The default mode should be to echo all test debug output to a separate
#   file, based on the test name (TODO: detect accidentally duplicated test
#   names).  But when you specify it a particular test, you can see debug logs.
# - Set TASTE_TMP for each test?  Or just call it TMP or TTMP maybe?

# - print output in a parseable format
#   - have an option to print timing summaries, so you can optimize slow tests.
#   And also print them in a TABLE/csv, so you can analyze test runs easily.

gen-run-script() {
  local test_file=$1
    # Generate a script to run all tests we find
  local tests=$(grep --extended --only-matching '^test-[^(]+' $0)

  # include all the tests
  cat <<EOF
. $test_file

num_passed=0
num_failed=0

# Whether to stop after the first failure.
TASTE_FAIL_FAST=\${TASTE_FAIL_FAST:-}
EOF


  for t in $tests; do
    cat <<EOF
echo
echo ---------------------------------------------------------------------
echo      $t
echo

$t
if test \$? -eq 0; then
  num_passed=\$((num_passed+1))
else
  num_failed=\$((num_failed+1))
  if test "\$TASTE_FAIL_FAST" = 1; then
    log "FAIL after \$num_passed tests passed"
    exit 1
  fi
fi

# || die $t failed"
EOF
  done

  cat <<EOF
. $test_file

echo =====================================================================
echo "\$num_passed passed, \$num_failed failed."
EOF
}

# grep the current file
taste-main() { 
  # Prevent double execution.  At the bottom of every test file is 'taste-main
  # "$@"'.  But then we generate gen.sh, which will source the script.

  local disable=${TASTE_NO_RUN:-}
  if test "$disable" = 1; then
    echo "RETURN"
    return
  fi
  export TASTE_NO_RUN=1

  # with no arguments, run the whole file
  if test $# -eq 0; then
    echo run all $0
    mkdir -p _tmp
    gen-run-script $0 > _tmp/_gen.sh
    chmod +x _tmp/_gen.sh
    # PROBLEM: This is fragile because it changes $0.  Frequently you will
    # locate executables with $0.
    _tmp/_gen.sh
  else
    # Run a function with given args.
    "$@"
  fi
}

